/*
 * @[H]:  Copyright (c) 2021 Phytium Information Technology, Inc. 
 * 
 *  SPDX-License-Identifier: Apache-2.0. 
 * 
 * @Date: 2021-08-17 10:54:12
 * @LastEditTime: 2021-08-25 16:48:16
 * @Description:  Description of file
 * @Modify History: 
 * * * Ver   Who        Date         Changes
 * * ----- ------     --------    --------------------------------------
 */
#include "fgdma.h"
#include "fgdma_hw.h"
#include "fgdma_bdl.h"
#include "kernel.h"
#include <string.h>

#define DMA_CX_WRBURST(x) min((x), (u32)0x3)
#define DMA_CX_WRSIZE(x) (min((x), (u32)0x7) << 4)
#define DMA_CX_WRLEN(x) (min((x), (u32)0xFF) << 8)
#define DMA_CX_RDBURST(x) (min((x), (u32)0x3) << 16)
#define DMA_CX_RDSIZE(x) (min((x), (u32)0x7) << 20)
#define DMA_CX_RDLEN(x) (min((x), (u32)0xFF) << 24)

#define DMA_CX_BDL BIT(0)
#define DMA_CX_BDL_ROLL BIT(4)
#define DMA_CX_WR_QOS_EN BIT(8)
#define DMA_CX_WR_QOS(x) (min((x), (u32)0xF) << 12)
#define DMA_CX_RD_QOS_EN BIT(16)
#define DMA_CX_RD_QOS(x) (min((x), (u32)0xF) << 20)

static u32 FGdmaBrustSize(u32 brust_width)
{
    u32 size = 0;

    switch (brust_width)
    {
    case FGDMA_SLAVE_BUSWIDTH_1_BYTE:
        size = 0;
        break;
    case FGDMA_SLAVE_BUSWIDTH_2_BYTES:
        size = 1;
        break;
    case FGDMA_SLAVE_BUSWIDTH_4_BYTES:
        size = 2;
        break;
    case FGDMA_SLAVE_BUSWIDTH_8_BYTES:
        size = 3;
        break;
    case FGDMA_SLAVE_BUSWIDTH_16_BYTES:
        size = 4;
        break;
    default:
        size = 2;
        break;
    }

    return size;
}

static inline void FGdmaChSendConfig(FGdma *instance_p,
                                     FGdmaChIndex index,
                                     u32 direction,
                                     u32 *xfer_cfg,
                                     u32 *dma_mode)
{
    u32 brust_width;
    u32 dma_ctl;
    *dma_mode = 0;

    brust_width = FGdmaBrustSize(instance_p->config.dma_channel_config[index].src_addr_width);
    *xfer_cfg = DMA_CX_RDSIZE(brust_width);
    brust_width = FGdmaBrustSize(instance_p->config.dma_channel_config[index].dst_addr_width);
    *xfer_cfg |= DMA_CX_WRSIZE(brust_width);
    brust_width = FGdmaBrustSize(instance_p->config.dma_channel_config[index].src_maxburst);
    *xfer_cfg |= DMA_CX_RDLEN(brust_width);
    brust_width = FGdmaBrustSize(instance_p->config.dma_channel_config[index].dst_maxburst);
    *xfer_cfg |= DMA_CX_WRLEN(brust_width);

    if (direction == FGDMA_MEM_TO_MEM)
    {
        *xfer_cfg |= DMA_CX_WRBURST((u32)1);
        *xfer_cfg |= DMA_CX_RDBURST((u32)1);
    }
    else if (direction == FGDMA_MEM_TO_DEV)
    {
        *xfer_cfg |= DMA_CX_WRBURST((u32)0);
        *xfer_cfg |= DMA_CX_RDBURST((u32)1);
    }
    else if (direction == FGDMA_DEV_TO_MEM)
    {
        *xfer_cfg |= DMA_CX_WRBURST((u32)1);
        *xfer_cfg |= DMA_CX_RDBURST((u32)0);
    }

    dma_ctl = FGDMA_READREG(instance_p->config.base_address, FGDMA_GLOBAL_CTL_OFFSET);
    if (dma_ctl & FGDMA_GLOBAL_CTL_RD_ARB_MASK)
    {
        *dma_mode |= DMA_CX_RD_QOS(instance_p->config.dma_channel_config[index].priority);
        *dma_mode |= DMA_CX_RD_QOS_EN;
    }

    if (dma_ctl & FGDMA_GLOBAL_CTL_WR_ARB_MASK)
    {
        *dma_mode |= DMA_CX_WR_QOS(instance_p->config.dma_channel_config[index].priority);
        *dma_mode |= DMA_CX_WR_QOS_EN;
    }
}

ft_error_t FGdmaBdlTransfer(FGdma *instance_p, FGdmaChIndex index, u32 direction,
                            struct FGdmaBdlDesc *bdl_desc_p, u32 desc_num)
{
    u32 xfer_cfg;
    u32 dma_mode = 0;
    u32 i = 0;
    FT_ASSERTNONVOID(instance_p != NULL);
    FT_ASSERTNONVOID(instance_p->is_ready == FT_COMPONENT_IS_READY);
    FT_ASSERTNONVOID(index < FGDMA_CHANNEL_MAX_NUM);
    FT_ASSERTNONVOID(direction <= FGDMA_DEV_TO_MEM);

    FGdmaChSendConfig(instance_p, index, direction, &xfer_cfg, &dma_mode);
    dma_mode |= DMA_CX_BDL; // Use bdl mode

    for (i = 0; i < desc_num; i++)
    {
        bdl_desc_p[i].desc_dst_tc = xfer_cfg & 0xffff;
        bdl_desc_p[i].desc_src_tc = (xfer_cfg >> 16) & 0xffff;
    }

    FGDMA_WRITEREG(instance_p->config.dma_channel_config[index].dma_channel_addr, FGDMA_CHX_MODE_OFFSET, dma_mode);
    FGDMA_WRITEREG(instance_p->config.dma_channel_config[index].dma_channel_addr, FGDMA_CHX_XFER_CFG_OFFSET, xfer_cfg);

#ifdef __aarch64__
    FGDMA_WRITEREG(instance_p->config.dma_channel_config[index].dma_channel_addr, FGDMA_CHX_UPSADDR_OFFSET, (u32)(((uintptr)bdl_desc_p >> 32) & 0xffffffffU));
#else
    FGDMA_WRITEREG(instance_p->config.dma_channel_config[index].dma_channel_addr, FGDMA_CHX_UPSADDR_OFFSET, 0);
#endif
    FGDMA_WRITEREG(instance_p->config.dma_channel_config[index].dma_channel_addr, FGDMA_CHX_LWSADDR_OFFSET, (u32)((uintptr)bdl_desc_p & 0xffffffffU));
    FGDMA_WRITEREG(instance_p->config.dma_channel_config[index].dma_channel_addr, FGDMA_CHX_LVI_OFFSET, desc_num - 1);
    FGdmaChEnable(instance_p, index);
    //FGdmaChBdlIrqEnable(instance_p, index);
    FGdmaChIrqEnable(instance_p, index);

    return (FT_SUCCESS);
}

ft_error_t FGdmaTransfer(FGdma *instance_p, FGdmaChIndex index,
                         FGdmaDataConfig *data_config_p)
{
    u32 brust_width;
    u32 xfer_cfg;
    u32 dma_ctl, dma_mode = 0;
    FT_ASSERTNONVOID(instance_p != NULL);
    FT_ASSERTNONVOID(instance_p->is_ready == FT_COMPONENT_IS_READY);
    FT_ASSERTNONVOID(index < FGDMA_CHANNEL_MAX_NUM);
    FT_ASSERTNONVOID(data_config_p->direction <= FGDMA_DEV_TO_MEM);

    FGdmaChSendConfig(instance_p, index, data_config_p->direction, &xfer_cfg, &dma_mode);
    dma_mode &= ~DMA_CX_BDL; // Use direct mode

    FGDMA_WRITEREG(instance_p->config.dma_channel_config[index].dma_channel_addr, FGDMA_CHX_TS_OFFSET, data_config_p->data_len);
    FGDMA_WRITEREG(instance_p->config.dma_channel_config[index].dma_channel_addr, FGDMA_CHX_UPDADDR_OFFSET, data_config_p->dma_dst_addr_h);
    FGDMA_WRITEREG(instance_p->config.dma_channel_config[index].dma_channel_addr, FGDMA_CHX_LWDADDR_OFFSET, data_config_p->dma_dst_addr_l);

    FGDMA_WRITEREG(instance_p->config.dma_channel_config[index].dma_channel_addr, FGDMA_CHX_MODE_OFFSET, dma_mode);

    FGDMA_WRITEREG(instance_p->config.dma_channel_config[index].dma_channel_addr, FGDMA_CHX_UPSADDR_OFFSET, data_config_p->dma_src_addr_h);
    FGDMA_WRITEREG(instance_p->config.dma_channel_config[index].dma_channel_addr, FGDMA_CHX_LWSADDR_OFFSET, data_config_p->dma_src_addr_l);

    FGDMA_WRITEREG(instance_p->config.dma_channel_config[index].dma_channel_addr, FGDMA_CHX_XFER_CFG_OFFSET, xfer_cfg);

    /* irq */
    FGDMA_WRITEREG(instance_p->config.dma_channel_config[index].dma_channel_addr, FGDMA_CHX_INT_CTRL_OFFSET, FGDMA_CHX_INT_CTRL_TRANS_END_ENABLE_MASK);
    FGdmaChEnable(instance_p, index);
    FGdmaChIrqEnable(instance_p, index);

    return (FT_SUCCESS);
}

ft_error_t FGdmaCfgInitialize(FGdma *instance_p, FGdmaConfig *config_p)
{
    fsize_t i;

    /* Assert arguments */
    FT_ASSERTNONVOID(instance_p != NULL);
    FT_ASSERTNONVOID(config_p != NULL);

    /* Clear instance memory and make copy of configuration */
    memset(instance_p, 0, sizeof(FGdma));
    memcpy(&instance_p->config, config_p, sizeof(FGdmaConfig));

    instance_p->is_ready = FT_COMPONENT_IS_READY;
    FGdmaHwInit(instance_p);

    return (FT_SUCCESS);
}